home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / SOAP / WSDL.php < prev   
Encoding:
PHP Script  |  2007-12-20  |  87.1 KB  |  2,275 lines

  1. <?php
  2. /**
  3.  * This file contains the code for dealing with WSDL access and services.
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 2.02 of the PHP license,
  8.  * that is bundled with this package in the file LICENSE, and is available at
  9.  * through the world-wide-web at http://www.php.net/license/2_02.txt.  If you
  10.  * did not receive a copy of the PHP license and are unable to obtain it
  11.  * through the world-wide-web, please send a note to license@php.net so we can
  12.  * mail you a copy immediately.
  13.  *
  14.  * @category   Web Services
  15.  * @package    SOAP
  16.  * @author     Dietrich Ayala <dietrich@ganx4.com> Original Author
  17.  * @author     Shane Caraveo <Shane@Caraveo.com>   Port to PEAR and more
  18.  * @author     Chuck Hagenbuch <chuck@horde.org>   Maintenance
  19.  * @author     Jan Schneider <jan@horde.org>       Maintenance
  20.  * @copyright  2003-2005 The PHP Group
  21.  * @license    http://www.php.net/license/2_02.txt  PHP License 2.02
  22.  * @link       http://pear.php.net/package/SOAP
  23.  */
  24.  
  25. require_once 'SOAP/Base.php';
  26. require_once 'SOAP/Fault.php';
  27. require_once 'HTTP/Request.php';
  28.  
  29. define('WSDL_CACHE_MAX_AGE', 43200);
  30.  
  31. /**
  32.  * This class parses WSDL files, and can be used by SOAP::Client to properly
  33.  * register soap values for services.
  34.  *
  35.  * Originally based on SOAPx4 by Dietrich Ayala
  36.  * http://dietrich.ganx4.com/soapx4
  37.  *
  38.  * @todo
  39.  * - add wsdl caching
  40.  * - refactor namespace handling ($namespace/$ns)
  41.  * - implement IDL type syntax declaration so we can generate WSDL
  42.  *
  43.  * @access public
  44.  * @package SOAP
  45.  * @author Shane Caraveo <shane@php.net> Conversion to PEAR and updates
  46.  * @author Dietrich Ayala <dietrich@ganx4.com> Original Author
  47.  */
  48. class SOAP_WSDL extends SOAP_Base
  49. {
  50.     var $tns = null;
  51.     var $definition = array();
  52.     var $namespaces = array();
  53.     var $ns = array();
  54.     var $xsd = SOAP_XML_SCHEMA_VERSION;
  55.     var $complexTypes = array();
  56.     var $elements = array();
  57.     var $messages = array();
  58.     var $portTypes = array();
  59.     var $bindings = array();
  60.     var $imports = array();
  61.     var $services = array();
  62.     var $service = '';
  63.  
  64.     /**
  65.      * URL to WSDL file.
  66.      *
  67.      * @var string
  68.      */
  69.     var $uri;
  70.  
  71.     /**
  72.      * Parse documentation in the WSDL?
  73.      *
  74.      * @var boolean
  75.      */
  76.     var $docs;
  77.  
  78.     /**
  79.      * Proxy parameters.
  80.      *
  81.      * @var array
  82.      */
  83.     var $proxy;
  84.  
  85.     /**
  86.      * Enable tracing in the generated proxy class?
  87.      *
  88.      * @var boolean
  89.      */
  90.     var $trace = false;
  91.  
  92.     /**
  93.      * Use WSDL cache?
  94.      *
  95.      * @var boolean
  96.      */
  97.     var $cacheUse;
  98.  
  99.     /**
  100.      * WSDL cache directory.
  101.      *
  102.      * @var string
  103.      */
  104.     var $cacheDir;
  105.  
  106.     /**
  107.      * Cache maximum lifetime (in seconds).
  108.      *
  109.      * @var integer
  110.      */
  111.     var $cacheMaxAge;
  112.  
  113.     /**
  114.      * Class to use for WSDL parsing. Can be overridden for special cases,
  115.      * subclasses, etc.
  116.      *
  117.      * @var string
  118.      */
  119.     var $wsdlParserClass = 'SOAP_WSDL_Parser';
  120.  
  121.     /**
  122.      * Reserved PHP keywords.
  123.      *
  124.      * @link http://www.php.net/manual/en/reserved.php
  125.      *
  126.      * @var array
  127.      */
  128.     var $_reserved = array('abstract', 'and', 'array', 'as', 'break', 'case',
  129.                            'catch', 'cfunction', 'class', 'clone', 'const',
  130.                            'continue', 'declare', 'default', 'die', 'do',
  131.                            'echo', 'else', 'elseif', 'empty', 'enddeclare',
  132.                            'endfor', 'endforeach', 'endif', 'endswitch',
  133.                            'endwhile', 'eval', 'exception', 'exit', 'extends',
  134.                            'final', 'for', 'foreach', 'function', 'global',
  135.                            'if', 'implements', 'include', 'include_once',
  136.                            'interface', 'isset', 'list', 'new', 'old_function',
  137.                            'or', 'php_user_filter', 'print', 'private',
  138.                            'protected', 'public', 'require', 'require_once',
  139.                            'return', 'static', 'switch', 'this', 'throw',
  140.                            'try', 'unset', 'use', 'var', 'while', 'xor');
  141.  
  142.     /**
  143.      * Regular expressions for invalid PHP labels.
  144.      *
  145.      * @link http://www.php.net/manual/en/language.variables.php.
  146.      *
  147.      * @var string
  148.      */
  149.     var $_invalid = array('/^[^a-zA-Z_\x7f-\xff]/', '/[^a-zA-Z0-9_\x7f-\xff]/');
  150.  
  151.     /**
  152.      * SOAP_WSDL constructor.
  153.      *
  154.      * @param string $wsdl_uri          URL to WSDL file.
  155.      * @param array $proxy              Options for HTTP_Request class
  156.      *                                  @see HTTP_Request.
  157.      * @param boolean|string $cacheUse  Use WSDL caching? The cache directory
  158.      *                                  if a string.
  159.      * @param integer $cacheMaxAge      Cache maximum lifetime (in seconds).
  160.      * @param boolean $docs             Parse documentation in the WSDL?
  161.      *
  162.      * @access public
  163.      */
  164.     function SOAP_WSDL($wsdl_uri    = false,
  165.                        $proxy       = array(),
  166.                        $cacheUse    = false,
  167.                        $cacheMaxAge = WSDL_CACHE_MAX_AGE,
  168.                        $docs        = false)
  169.     {
  170.         parent::SOAP_Base('WSDL');
  171.         $this->uri         = $wsdl_uri;
  172.         $this->proxy       = $proxy;
  173.         $this->cacheUse    = !empty($cacheUse);
  174.         $this->cacheMaxAge = $cacheMaxAge;
  175.         $this->docs        = $docs;
  176.         if (is_string($cacheUse)) {
  177.             $this->cacheDir = $cacheUse;
  178.         }
  179.  
  180.         if ($wsdl_uri) {
  181.             if (!PEAR::isError($this->parseURL($wsdl_uri))) {
  182.                 reset($this->services);
  183.                 $this->service = key($this->services);
  184.             }
  185.         }
  186.     }
  187.  
  188.     /**
  189.      * @deprecated  Use setService().
  190.      */
  191.     function set_service($service)
  192.     {
  193.         $this->setService($service);
  194.     }
  195.  
  196.     /**
  197.      * Sets the service currently to be used.
  198.      *
  199.      * @param string $service  An (existing) service name.
  200.      */
  201.     function setService($service)
  202.     {
  203.         if (array_key_exists($service, $this->services)) {
  204.             $this->service = $service;
  205.         }
  206.     }
  207.  
  208.     /**
  209.      * Fills the WSDL array tree with data from a WSDL file.
  210.      *
  211.      * @param string $wsdl_uri  URL to WSDL file.
  212.      * @param array $proxy      Contains options for HTTP_Request class
  213.      *                          @see HTTP_Request.
  214.      */
  215.     function parseURL($wsdl_uri, $proxy = array())
  216.     {
  217.         $parser =& new $this->wsdlParserClass($wsdl_uri, $this, $this->docs);
  218.  
  219.         if ($parser->fault) {
  220.             $this->_raiseSoapFault($parser->fault);
  221.         }
  222.     }
  223.  
  224.     /**
  225.      * Fills the WSDL array tree with data from one or more PHP class objects.
  226.      *
  227.      * @param mixed $wsdl_obj          An object or array of objects to add to
  228.      *                                 the internal WSDL tree.
  229.      * @param string $targetNamespace  The target namespace of schema types
  230.      *                                 etc.
  231.      * @param string $service_name     Name of the WSDL service.
  232.      * @param string $service_desc     Optional description of the WSDL
  233.      *                                 service.
  234.      */
  235.     function parseObject(&$wsdl_obj, $targetNamespace, $service_name,
  236.                          $service_desc = '')
  237.     {
  238.         $parser =& new SOAP_WSDL_ObjectParser($wsdl_obj, $this,
  239.                                               $targetNamespace, $service_name,
  240.                                               $service_desc);
  241.  
  242.          if ($parser->fault) {
  243.              $this->_raiseSoapFault($parser->fault);
  244.          }
  245.     }
  246.  
  247.     function getEndpoint($portName)
  248.     {
  249.         if ($this->_isfault()) {
  250.             return $this->_getfault();
  251.         }
  252.  
  253.         return (isset($this->services[$this->service]['ports'][$portName]['address']['location']))
  254.                 ? $this->services[$this->service]['ports'][$portName]['address']['location']
  255.                 : $this->_raiseSoapFault("No endpoint for port for $portName", $this->uri);
  256.     }
  257.  
  258.     function _getPortName($operation, $service)
  259.     {
  260.         if (isset($this->services[$service]['ports'])) {
  261.             $ports = $this->services[$service]['ports'];
  262.             foreach ($ports as $port => $portAttrs) {
  263.                 $type = $ports[$port]['type'];
  264.                 if ($type == 'soap' &&
  265.                     isset($this->bindings[$portAttrs['binding']]['operations'][$operation])) {
  266.                     return $port;
  267.                 }
  268.             }
  269.         }
  270.         return null;
  271.     }
  272.  
  273.     /**
  274.      * Finds the name of the first port that contains an operation of name
  275.      * $operation. Always returns a SOAP portName.
  276.      */
  277.     function getPortName($operation, $service = null)
  278.     {
  279.         if ($this->_isfault()) {
  280.             return $this->_getfault();
  281.         }
  282.  
  283.         if (!$service) {
  284.             $service = $this->service;
  285.         }
  286.         if (isset($this->services[$service]['ports'])) {
  287.             if ($portName = $this->_getPortName($operation, $service)) {
  288.                 return $portName;
  289.             }
  290.         }
  291.         // Try any service in the WSDL.
  292.         foreach ($this->services as $serviceName => $service) {
  293.             if (isset($this->services[$serviceName]['ports'])) {
  294.                 if ($portName = $this->_getPortName($operation, $serviceName)) {
  295.                     $this->service = $serviceName;
  296.                     return $portName;
  297.                 }
  298.             }
  299.         }
  300.         return $this->_raiseSoapFault("No operation $operation in WSDL.", $this->uri);
  301.     }
  302.  
  303.     function getOperationData($portName, $operation)
  304.     {
  305.         if ($this->_isfault()) {
  306.             return $this->_getfault();
  307.         }
  308.  
  309.         if (!isset($this->services[$this->service]['ports'][$portName]['binding']) ||
  310.             !($binding = $this->services[$this->service]['ports'][$portName]['binding'])) {
  311.             return $this->_raiseSoapFault("No binding for port $portName in WSDL.", $this->uri);
  312.         }
  313.  
  314.         // Get operation data from binding.
  315.         if (is_array($this->bindings[$binding]['operations'][$operation])) {
  316.             $opData = $this->bindings[$binding]['operations'][$operation];
  317.         }
  318.         // get operation data from porttype
  319.         $portType = $this->bindings[$binding]['type'];
  320.         if (!$portType) {
  321.             return $this->_raiseSoapFault("No port type for binding $binding in WSDL.", $this->uri);
  322.         }
  323.         if (is_array($type = $this->portTypes[$portType][$operation])) {
  324.             if (isset($type['parameterOrder'])) {
  325.                 $opData['parameterOrder'] = $type['parameterOrder'];
  326.             }
  327.             $opData['input'] = array_merge($opData['input'], $type['input']);
  328.             $opData['output'] = array_merge($opData['output'], $type['output']);
  329.         }
  330.         if (!$opData)
  331.             return $this->_raiseSoapFault("No operation $operation for port $portName in WSDL.", $this->uri);
  332.         $opData['parameters'] = false;
  333.         if (isset($this->bindings[$binding]['operations'][$operation]['input']['namespace']))
  334.             $opData['namespace'] = $this->bindings[$binding]['operations'][$operation]['input']['namespace'];
  335.         // Message data from messages.
  336.         $inputMsg = $opData['input']['message'];
  337.         if (is_array($this->messages[$inputMsg])) {
  338.             foreach ($this->messages[$inputMsg] as $pname => $pattrs) {
  339.                 if ($opData['style'] == 'document' &&
  340.                     $opData['input']['use'] == 'literal' &&
  341.                     $pname == 'parameters') {
  342.                     $opData['parameters'] = true;
  343.                     $opData['namespace'] = $this->namespaces[$pattrs['namespace']];
  344.                     $el = $this->elements[$pattrs['namespace']][$pattrs['type']];
  345.                     if (isset($el['elements'])) {
  346.                         foreach ($el['elements'] as $elname => $elattrs) {
  347.                             $opData['input']['parts'][$elname] = $elattrs;
  348.                         }
  349.                     }
  350.                 } else {
  351.                     $opData['input']['parts'][$pname] = $pattrs;
  352.                 }
  353.             }
  354.         }
  355.         $outputMsg = $opData['output']['message'];
  356.         if (is_array($this->messages[$outputMsg])) {
  357.             foreach ($this->messages[$outputMsg] as $pname => $pattrs) {
  358.                 if ($opData['style'] == 'document' &&
  359.                     $opData['output']['use'] == 'literal' &&
  360.                     $pname == 'parameters') {
  361.  
  362.                     $el = $this->elements[$pattrs['namespace']][$pattrs['type']];
  363.                     if (isset($el['elements'])) {
  364.                         foreach ($el['elements'] as $elname => $elattrs) {
  365.                             $opData['output']['parts'][$elname] = $elattrs;
  366.                         }
  367.                     }
  368.                 } else {
  369.                     $opData['output']['parts'][$pname] = $pattrs;
  370.                 }
  371.             }
  372.         }
  373.         return $opData;
  374.     }
  375.  
  376.     function matchMethod(&$operation)
  377.     {
  378.         if ($this->_isfault()) {
  379.             return $this->_getfault();
  380.         }
  381.  
  382.         // Overloading lowercases function names :(
  383.         foreach ($this->services[$this->service]['ports'] as $port => $portAttrs) {
  384.             foreach (array_keys($this->bindings[$portAttrs['binding']]['operations']) as $op) {
  385.                 if (strcasecmp($op, $operation) == 0) {
  386.                     $operation = $op;
  387.                 }
  388.             }
  389.         }
  390.     }
  391.  
  392.     /**
  393.      * Given a datatype, what function handles the processing?
  394.      *
  395.      * This is used for doc/literal requests where we receive a datatype, and
  396.      * we need to pass it to a method in out server class.
  397.      *
  398.      * @param string $datatype
  399.      * @param string $namespace
  400.      * @return string
  401.      * @access public
  402.      */
  403.     function getDataHandler($datatype, $namespace)
  404.     {
  405.         // See if we have an element by this name.
  406.         if (isset($this->namespaces[$namespace])) {
  407.             $namespace = $this->namespaces[$namespace];
  408.         }
  409.  
  410.         if (isset($this->ns[$namespace])) {
  411.             $nsp = $this->ns[$namespace];
  412.             //if (!isset($this->elements[$nsp]))
  413.             //    $nsp = $this->namespaces[$nsp];
  414.             if (isset($this->elements[$nsp][$datatype])) {
  415.                 $checkmessages = array();
  416.                 // Find what messages use this datatype.
  417.                 foreach ($this->messages as $messagename => $message) {
  418.                     foreach ($message as $partname => $part) {
  419.                         if ($part['type'] == $datatype) {
  420.                             $checkmessages[] = $messagename;
  421.                             break;
  422.                         }
  423.                     }
  424.                 }
  425.                 // Find the operation that uses this message.
  426.                 $dataHandler = null;
  427.                 foreach($this->portTypes as $portname => $porttype) {
  428.                     foreach ($porttype as $opname => $opinfo) {
  429.                         foreach ($checkmessages as $messagename) {
  430.                             if ($opinfo['input']['message'] == $messagename) {
  431.                                 return $opname;
  432.                             }
  433.                         }
  434.                     }
  435.                 }
  436.             }
  437.         }
  438.  
  439.         return null;
  440.     }
  441.  
  442.     function getSoapAction($portName, $operation)
  443.     {
  444.         if ($this->_isfault()) {
  445.             return $this->_getfault();
  446.         }
  447.  
  448.         if (!empty($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction'])) {
  449.             return $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['soapAction'];
  450.         }
  451.  
  452.         return false;
  453.     }
  454.  
  455.     function getNamespace($portName, $operation)
  456.     {
  457.         if ($this->_isfault()) {
  458.             return $this->_getfault();
  459.         }
  460.  
  461.         if (!empty($this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace'])) {
  462.             return $this->bindings[$this->services[$this->service]['ports'][$portName]['binding']]['operations'][$operation]['input']['namespace'];
  463.         }
  464.  
  465.         return false;
  466.     }
  467.  
  468.     function getNamespaceAttributeName($namespace)
  469.     {
  470.         /* If it doesn't exist at first, flip the array and check again. */
  471.         if (empty($this->ns[$namespace])) {
  472.             $this->ns = array_flip($this->namespaces);
  473.         }
  474.  
  475.         /* If it doesn't exist now, add it. */
  476.         if (empty($this->ns[$namespace])) {
  477.             return $this->addNamespace($namespace);
  478.         }
  479.  
  480.         return $this->ns[$namespace];
  481.     }
  482.  
  483.     function addNamespace($namespace)
  484.     {
  485.         if (!empty($this->ns[$namespace])) {
  486.             return $this->ns[$namespace];
  487.         }
  488.  
  489.         $n = count($this->ns);
  490.         $attr = 'ns' . $n;
  491.         $this->namespaces['ns' . $n] = $namespace;
  492.         $this->ns[$namespace] = $attr;
  493.  
  494.         return $attr;
  495.     }
  496.  
  497.     function _validateString($string)
  498.     {
  499.         return preg_match('/^[\w_:#\/]+$/', $string);
  500.     }
  501.  
  502.     function _addArg(&$args, &$argarray, $argname)
  503.     {
  504.         if ($args) {
  505.             $args .= ', ';
  506.         }
  507.         $args .= '$' . $argname;
  508.         if (!$this->_validateString($argname)) {
  509.             return;
  510.         }
  511.         if ($argarray) {
  512.             $argarray .= ', ';
  513.         }
  514.         $argarray .= "'$argname' => $" . $argname;
  515.     }
  516.  
  517.     function _elementArg(&$args, &$argarray, &$_argtype, $_argname)
  518.     {
  519.         $comments = '';
  520.         $el = $this->elements[$_argtype['namespace']][$_argtype['type']];
  521.         $tns = isset($this->ns[$el['namespace']])
  522.             ? $this->ns[$el['namespace']]
  523.             : $_argtype['namespace'];
  524.  
  525.         if (!empty($el['complex']) ||
  526.             (isset($el['type']) &&
  527.              isset($this->complexTypes[$tns][$el['type']]))) {
  528.             // The element is a complex type.
  529.             $comments .= "        // {$_argtype['type']} is a ComplexType, refer to the WSDL for more info.\n";
  530.             $attrname = "{$_argtype['type']}_attr";
  531.             if (isset($el['type']) &&
  532.                 isset($this->complexTypes[$tns][$el['type']]['attribute'])) {
  533.                 $comments .= "        // {$_argtype['type']} may require attributes, refer to the WSDL for more info.\n";
  534.             }
  535.             $comments .= "        \${$attrname}['xmlns'] = '{$this->namespaces[$_argtype['namespace']]}';\n";
  536.             $comments .= "        \${$_argtype['type']} =& new SOAP_Value('{$_argtype['type']}', false, \${$_argtype['type']}, \$$attrname);\n";
  537.             $this->_addArg($args, $argarray, $_argtype['type']);
  538.             if (isset($el['type']) &&
  539.                 isset($this->complexTypes[$tns][$el['type']]['attribute'])) {
  540.                 if ($args) {
  541.                     $args .= ', ';
  542.                 }
  543.                 $args .= '$' . $attrname;
  544.             }
  545.         } elseif (isset($el['elements'])) {
  546.             foreach ($el['elements'] as $ename => $element) {
  547.                 $comments .= "        \$$ename =& new SOAP_Value('{{$this->namespaces[$element['namespace']]}}$ename', '" .
  548.                     (isset($element['type']) ? $element['type'] : false) .
  549.                     "', \$$ename);\n";
  550.                 $this->_addArg($args, $argarray, $ename);
  551.             }
  552.         } else {
  553.             $comments .= "        \$$_argname =& new SOAP_Value('{{$this->namespaces[$tns]}}$_argname', '{$el['type']}', \$$_argname);\n";
  554.             $this->_addArg($args, $argarray, $_argname);
  555.         }
  556.  
  557.         return $comments;
  558.     }
  559.  
  560.     function _complexTypeArg(&$args, &$argarray, &$_argtype, $_argname)
  561.     {
  562.         $comments = '';
  563.         if (isset($this->complexTypes[$_argtype['namespace']][$_argtype['type']])) {
  564.             $comments  = "        // $_argname is a ComplexType {$_argtype['type']},\n" .
  565.                 "        // refer to wsdl for more info\n";
  566.             if (isset($this->complexTypes[$_argtype['namespace']][$_argtype['type']]['attribute'])) {
  567.                 $comments .= "        // $_argname may require attributes, refer to wsdl for more info\n";
  568.             }
  569.             $wrapname = '{' . $this->namespaces[$_argtype['namespace']].'}' . $_argtype['type'];
  570.             $comments .= "        \$$_argname =& new SOAP_Value('$_argname', '$wrapname', \$$_argname);\n";
  571.         }
  572.  
  573.         $this->_addArg($args, $argarray, $_argname);
  574.  
  575.         return $comments;
  576.     }
  577.  
  578.     /**
  579.      * Generates stub code from the WSDL that can be saved to a file or eval'd
  580.      * into existence.
  581.      */
  582.     function generateProxyCode($port = '', $classname = '')
  583.     {
  584.         if ($this->_isfault()) {
  585.             return $this->_getfault();
  586.         }
  587.  
  588.         $multiport = count($this->services[$this->service]['ports']) > 1;
  589.         if (!$port) {
  590.             reset($this->services[$this->service]['ports']);
  591.             $port = current($this->services[$this->service]['ports']);
  592.         }
  593.         // XXX currently do not support HTTP ports
  594.         if ($port['type'] != 'soap') {
  595.             return null;
  596.         }
  597.  
  598.         // XXX currentPort is BAD
  599.         $clienturl = $port['address']['location'];
  600.         if (!$classname) {
  601.             if ($multiport || $port) {
  602.                 $classname = 'WebService_' . $this->service . '_' . $port['name'];
  603.             } else {
  604.                 $classname = 'WebService_' . $this->service;
  605.             }
  606.             $classname = $this->_sanitize($classname);
  607.         }
  608.  
  609.         if (!$this->_validateString($classname)) {
  610.             return null;
  611.         }
  612.  
  613.         if (is_array($this->proxy) && count($this->proxy)) {
  614.             $class = "class $classname extends SOAP_Client\n{\n" .
  615.             "    function $classname(\$path = '$clienturl')\n    {\n" .
  616.             "        \$this->SOAP_Client(\$path, 0, 0,\n" .
  617.             '                           array(';
  618.  
  619.             foreach ($this->proxy as $key => $val) {
  620.                 if (is_array($val)) {
  621.                     $class .= "'$key' => array(";
  622.                     foreach ($val as $key2 => $val2) {
  623.                         $class .= "'$key2' => '$val2', ";
  624.                     }
  625.                     $class .= ')';
  626.                 } else {
  627.                     $class .= "'$key' => '$val', ";
  628.                 }
  629.             }
  630.             $class .= "));\n    }\n";
  631.             $class = str_replace(', ))', '))', $class);
  632.         } else {
  633.             $class = "class $classname extends SOAP_Client\n{\n" .
  634.             "    function $classname(\$path = '$clienturl')\n    {\n" .
  635.             "        \$this->SOAP_Client(\$path, 0);\n" .
  636.             "    }\n";
  637.         }
  638.  
  639.         // Get the binding, from that get the port type.
  640.         $primaryBinding = $port['binding'];
  641.         $primaryBinding = preg_replace("/^(.*:)/", '', $primaryBinding);
  642.         $portType = $this->bindings[$primaryBinding]['type'];
  643.         $portType = preg_replace("/^(.*:)/", '', $portType);
  644.         $style = $this->bindings[$primaryBinding]['style'];
  645.  
  646.         // XXX currentPortType is BAD
  647.         foreach ($this->portTypes[$portType] as $opname => $operation) {
  648.             $binding = $this->bindings[$primaryBinding]['operations'][$opname];
  649.             if (isset($binding['soapAction'])) {
  650.                 $soapaction = $binding['soapAction'];
  651.             } else {
  652.                 $soapaction = null;
  653.             }
  654.             if (isset($binding['style'])) {
  655.                 $opstyle = $binding['style'];
  656.             } else {
  657.                 $opstyle = $style;
  658.             }
  659.             $use = $binding['input']['use'];
  660.             if ($use == 'encoded') {
  661.                 $namespace = $binding['input']['namespace'];
  662.             } else {
  663.                 $bindingType = $this->bindings[$primaryBinding]['type'];
  664.                 $ns = $this->portTypes[$bindingType][$opname]['input']['namespace'];
  665.                 $namespace = $this->namespaces[$ns];
  666.             }
  667.  
  668.             $args = '';
  669.             $argarray = '';
  670.             $comments = '';
  671.             $wrappers = '';
  672.             foreach ($operation['input'] as $argname => $argtype) {
  673.                 if ($argname == 'message') {
  674.                     foreach ($this->messages[$argtype] as $_argname => $_argtype) {
  675.                         $_argname = $this->_sanitize($_argname);
  676.                         if ($opstyle == 'document' && $use == 'literal' &&
  677.                             $_argtype['name'] == 'parameters') {
  678.                             // The type or element refered to is used for
  679.                             // parameters.
  680.                             $elattrs = null;
  681.                             $element = $_argtype['element'];
  682.                             $el = $this->elements[$_argtype['namespace']][$_argtype['type']];
  683.  
  684.                             if ($el['complex']) {
  685.                                 $namespace = $this->namespaces[$_argtype['namespace']];
  686.                                 // XXX need to wrap the parameters in a
  687.                                 // SOAP_Value.
  688.                             }
  689.                             if (isset($el['elements'])) {
  690.                                 foreach ($el['elements'] as $elname => $elattrs) {
  691.                                     $elname = $this->_sanitize($elname);
  692.                                     // Is the element a complex type?
  693.                                     if (isset($this->complexTypes[$elattrs['namespace']][$elname])) {
  694.                                         $comments .= $this->_complexTypeArg($args, $argarray, $_argtype, $_argname);
  695.                                     } else {
  696.                                         $this->_addArg($args, $argarray, $elname);
  697.                                     }
  698.                                 }
  699.                             }
  700.                             if ($el['complex'] && $argarray) {
  701.                                 $wrapname = '{' . $this->namespaces[$_argtype['namespace']].'}' . $el['name'];
  702.                                 $comments .= "        \${$el['name']} =& new SOAP_Value('$wrapname', false, \$v = array($argarray));\n";
  703.                                 $argarray = "'{$el['name']}' => \${$el['name']}";
  704.                             }
  705.                         } else {
  706.                             if (isset($_argtype['element'])) {
  707.                                 // Element argument.
  708.                                 $comments .= $this->_elementArg($args, $argarray, $_argtype, $_argtype['type']);
  709.                             } else {
  710.                                 // Complex type argument.
  711.                                 $comments .= $this->_complexTypeArg($args, $argarray, $_argtype, $_argname);
  712.                             }
  713.                         }
  714.                     }
  715.                 }
  716.             }
  717.  
  718.             // Validate entries.
  719.  
  720.             // Operation names are function names, so try to make sure it's
  721.             // legal. This could potentially cause collisions, but let's try
  722.             // to make everything callable and see how many problems that
  723.             // causes.
  724.             $opname_php = $this->_sanitize($opname);
  725.             if (!$this->_validateString($opname_php)) {
  726.                 return null;
  727.             }
  728.  
  729.             if ($argarray) {
  730.                 $argarray = "array($argarray)";
  731.             } else {
  732.                 $argarray = 'null';
  733.             }
  734.  
  735.             $class .= "    function &$opname_php($args)\n    {\n$comments$wrappers" .
  736.                 "        \$result = \$this->call('$opname',\n" .
  737.                 "                              \$v = $argarray,\n" .
  738.                 "                              array('namespace' => '$namespace',\n" .
  739.                 "                                    'soapaction' => '$soapaction',\n" .
  740.                 "                                    'style' => '$opstyle',\n" .
  741.                 "                                    'use' => '$use'" .
  742.                 ($this->trace ? ",\n                                    'trace' => true" : '') . "));\n" .
  743.                 "        return \$result;\n" .
  744.                 "    }\n";
  745.         }
  746.  
  747.         $class .= "}\n";
  748.  
  749.         return $class;
  750.     }
  751.  
  752.     function generateAllProxies()
  753.     {
  754.         $proxycode = '';
  755.         foreach (array_keys($this->services[$this->service]['ports']) as $key) {
  756.             $port =& $this->services[$this->service]['ports'][$key];
  757.             $proxycode .= $this->generateProxyCode($port);
  758.         }
  759.         return $proxycode;
  760.     }
  761.  
  762.     function &getProxy($port = '', $name = '')
  763.     {
  764.         if ($this->_isfault()) {
  765.             $fault =& $this->_getfault();
  766.             return $fault;
  767.         }
  768.  
  769.         $multiport = count($this->services[$this->service]['ports']) > 1;
  770.  
  771.         if (!$port) {
  772.             reset($this->services[$this->service]['ports']);
  773.             $port = current($this->services[$this->service]['ports']);
  774.         }
  775.  
  776.         if ($multiport || $port) {
  777.             $classname = 'WebService_' . $this->service . '_' . $port['name'];
  778.         } else {
  779.             $classname = 'WebService_' . $this->service;
  780.         }
  781.  
  782.         if ($name) {
  783.             $classname = $name . '_' . $classname;
  784.         }
  785.  
  786.         $classname = $this->_sanitize($classname);
  787.         if (!class_exists($classname)) {
  788.             $proxy = $this->generateProxyCode($port, $classname);
  789.             require_once 'SOAP/Client.php';
  790.             eval($proxy);
  791.         }
  792.         $proxy =& new $classname;
  793.  
  794.         return $proxy;
  795.     }
  796.  
  797.     /**
  798.      * Sanitizes a SOAP value, method or class name so that it can be used as
  799.      * a valid PHP identifier. Invalid characters are converted into
  800.      * underscores and reserved words are prefixed with an underscore.
  801.      *
  802.      * @param string $name  The identifier to sanitize.
  803.      *
  804.      * @return string  The sanitized identifier.
  805.      */
  806.     function _sanitize($name)
  807.     {
  808.         $name = preg_replace($this->_invalid, '_', $name);
  809.         if (in_array($name, $this->_reserved)) {
  810.             $name = '_' . $name;
  811.         }
  812.         return $name;
  813.     }
  814.  
  815.     function &_getComplexTypeForElement($name, $namespace)
  816.     {
  817.         $t = null;
  818.         if (isset($this->ns[$namespace]) &&
  819.             isset($this->elements[$this->ns[$namespace]][$name]['type'])) {
  820.  
  821.             $type = $this->elements[$this->ns[$namespace]][$name]['type'];
  822.             $ns = $this->elements[$this->ns[$namespace]][$name]['namespace'];
  823.  
  824.             if (isset($this->complexTypes[$ns][$type])) {
  825.                 $t = $this->complexTypes[$ns][$type];
  826.             }
  827.         }
  828.         return $t;
  829.     }
  830.  
  831.     function getComplexTypeNameForElement($name, $namespace)
  832.     {
  833.         $t = $this->_getComplexTypeForElement($name, $namespace);
  834.         if ($t) {
  835.             return $t['name'];
  836.         }
  837.         return null;
  838.     }
  839.  
  840.     function getComplexTypeChildType($ns, $name, $child_ns, $child_name)
  841.     {
  842.         // Is the type an element?
  843.         $t = $this->_getComplexTypeForElement($name, $ns);
  844.         if ($t) {
  845.             // No, get it from complex types directly.
  846.             if (isset($t['elements'][$child_name]['type']))
  847.                 return $t['elements'][$child_name]['type'];
  848.         } elseif (isset($this->ns[$ns]) &&
  849.                   isset($this->elements[$this->ns[$ns]][$name]['complex']) &&
  850.                   $this->elements[$this->ns[$ns]][$name]['complex']) {
  851.             // Type is not an element but complex.
  852.             return $this->elements[$this->ns[$ns]][$name]['elements'][$child_name]['type'];
  853.         }
  854.         return null;
  855.     }
  856.  
  857.     function getSchemaType($type, $name, $type_namespace)
  858.     {
  859.         // see if it's a complex type so we can deal properly with
  860.         // SOAPENC:arrayType.
  861.         if ($name && $type) {
  862.             // XXX TODO:
  863.             // look up the name in the wsdl and validate the type.
  864.             foreach ($this->complexTypes as $ns => $types) {
  865.                 if (isset($types[$type])) {
  866.                     if (isset($types[$type]['type'])) {
  867.                         list($arraytype_ns, $arraytype, $array_depth) = isset($types[$type]['arrayType'])
  868.                             ? $this->_getDeepestArrayType($types[$type]['namespace'], $types[$type]['arrayType'])
  869.                             : array($this->namespaces[$types[$type]['namespace']], null, 0);
  870.                         return array($types[$type]['type'], $arraytype, $arraytype_ns, $array_depth);
  871.                     }
  872.                     if (isset($types[$type]['arrayType'])) {
  873.                         list($arraytype_ns, $arraytype, $array_depth) =
  874.                             $this->_getDeepestArrayType($types[$type]['namespace'], $types[$type]['arrayType']);
  875.                         return array('Array', $arraytype, $arraytype_ns, $array_depth);
  876.                     }
  877.                     if (!empty($types[$type]['elements'][$name])) {
  878.                         $type = $types[$type]['elements']['type'];
  879.                         return array($type, null, $this->namespaces[$types[$type]['namespace']], null);
  880.                     }
  881.                     break;
  882.                 }
  883.             }
  884.         }
  885.         if ($type && $type_namespace) {
  886.             $arrayType = null;
  887.             // XXX TODO:
  888.             // this code currently handles only one way of encoding array
  889.             // types in wsdl need to do a generalized function to figure out
  890.             // complex types
  891.             $p = $this->ns[$type_namespace];
  892.             if ($p && !empty($this->complexTypes[$p][$type])) {
  893.                 if ($arrayType = $this->complexTypes[$p][$type]['arrayType']) {
  894.                     $type = 'Array';
  895.                 } elseif ($this->complexTypes[$p][$type]['order'] == 'sequence' &&
  896.                           array_key_exists('elements', $this->complexTypes[$p][$type])) {
  897.                     reset($this->complexTypes[$p][$type]['elements']);
  898.                     // assume an array
  899.                     if (count($this->complexTypes[$p][$type]['elements']) == 1) {
  900.                         $arg = current($this->complexTypes[$p][$type]['elements']);
  901.                         $arrayType = $arg['type'];
  902.                         $type = 'Array';
  903.                     } else {
  904.                         foreach ($this->complexTypes[$p][$type]['elements'] as $element) {
  905.                             if ($element['name'] == $type) {
  906.                                 $arrayType = $element['type'];
  907.                                 $type = $element['type'];
  908.                             }
  909.                         }
  910.                     }
  911.                 } else {
  912.                     $type = 'Struct';
  913.                 }
  914.                 return array($type, $arrayType, $type_namespace, null);
  915.             }
  916.         }
  917.         return array(null, null, null, null);
  918.     }
  919.  
  920.     /**
  921.      * Recurse through the WSDL structure looking for the innermost array type
  922.      * of multi-dimensional arrays.
  923.      *
  924.      * Takes a namespace prefix and a type, which can be in the form 'type' or
  925.      * 'type[]', and returns the full namespace URI, the type of the most
  926.      * deeply nested array type found, and the number of levels of nesting.
  927.      *
  928.      * @access private
  929.      * @return mixed array or nothing
  930.      */
  931.     function _getDeepestArrayType($nsPrefix, $arrayType)
  932.     {
  933.         static $trail = array();
  934.  
  935.         $arrayType = ereg_replace('\[\]$', '', $arrayType);
  936.  
  937.         // Protect against circular references XXX We really need to remove
  938.         // trail from this altogether (it's very inefficient and in the wrong
  939.         // place!) and put circular reference checking in when the WSDL info
  940.         // is generated in the first place
  941.         if (array_search($nsPrefix . ':' . $arrayType, $trail)) {
  942.             return array(null, null, -count($trail));
  943.         }
  944.  
  945.         if (array_key_exists($nsPrefix, $this->complexTypes) &&
  946.             array_key_exists($arrayType, $this->complexTypes[$nsPrefix]) &&
  947.             array_key_exists('arrayType', $this->complexTypes[$nsPrefix][$arrayType])) {
  948.             $trail[] = $nsPrefix . ':' . $arrayType;
  949.             $result = $this->_getDeepestArrayType($this->complexTypes[$nsPrefix][$arrayType]['namespace'],
  950.                                                   $this->complexTypes[$nsPrefix][$arrayType]['arrayType']);
  951.             return array($result[0], $result[1], $result[2] + 1);
  952.         }
  953.         return array($this->namespaces[$nsPrefix], $arrayType, 0);
  954.     }
  955.  
  956. }
  957.  
  958. class SOAP_WSDL_Cache extends SOAP_Base
  959. {
  960.     /**
  961.      * Use WSDL cache?
  962.      *
  963.      * @var boolean
  964.      */
  965.     var $_cacheUse;
  966.  
  967.     /**
  968.      * WSDL cache directory.
  969.      *
  970.      * @var string
  971.      */
  972.     var $_cacheDir;
  973.  
  974.     /**
  975.      * Cache maximum lifetime (in seconds)
  976.      *
  977.      * @var integer
  978.      */
  979.     var $_cacheMaxAge;
  980.  
  981.     /**
  982.      * Constructor.
  983.      *
  984.      * @param boolean $cashUse      Use caching?
  985.      * @param integer $cacheMaxAge  Cache maximum lifetime (in seconds)
  986.      */
  987.     function SOAP_WSDL_Cache($cacheUse = false,
  988.                              $cacheMaxAge = WSDL_CACHE_MAX_AGE,
  989.                              $cacheDir = null)
  990.     {
  991.         parent::SOAP_Base('WSDLCACHE');
  992.         $this->_cacheUse = $cacheUse;
  993.         $this->_cacheDir = $cacheDir;
  994.         $this->_cacheMaxAge = $cacheMaxAge;
  995.     }
  996.  
  997.     /**
  998.      * Returns the path to the cache and creates it, if it doesn't exist.
  999.      *
  1000.      * @private
  1001.      *
  1002.      * @return string  The directory to use for the cache.
  1003.      */
  1004.     function _cacheDir()
  1005.     {
  1006.         if (!empty($this->_cacheDir)) {
  1007.             $dir = $this->_cacheDir;
  1008.         } else {
  1009.             $dir = getenv('WSDLCACHE');
  1010.             if (empty($dir)) {
  1011.                 $dir = './wsdlcache';
  1012.             }
  1013.         }
  1014.         @mkdir($dir, 0700);
  1015.         return $dir;
  1016.     }
  1017.  
  1018.     /**
  1019.      * Retrieves a file from cache if it exists, otherwise retreive from net,
  1020.      * add to cache, and return from cache.
  1021.      *
  1022.      * @param  string   URL to WSDL
  1023.      * @param  array    proxy parameters
  1024.      * @param  int      expected MD5 of WSDL URL
  1025.      * @access public
  1026.      * @return string  data
  1027.      */
  1028.     function get($wsdl_fname, $proxy_params = array(), $cache = 0)
  1029.     {
  1030.         $cachename = $md5_wsdl = $file_data = '';
  1031.         if ($this->_cacheUse) {
  1032.             // Try to retrieve WSDL from cache
  1033.             $cachename = $this->_cacheDir() . '/' . md5($wsdl_fname). ' .wsdl';
  1034.             if (file_exists($cachename)) {
  1035.                 $wf = fopen($cachename, 'rb');
  1036.                 if ($wf) {
  1037.                     // Reading cached file
  1038.                     $file_data = fread($wf, filesize($cachename));
  1039.                     $md5_wsdl = md5($file_data);
  1040.                     fclose($wf);
  1041.                 }
  1042.                 if ($cache) {
  1043.                     if ($cache != $md5_wsdl) {
  1044.                         return $this->_raiseSoapFault('WSDL Checksum error!', $wsdl_fname);
  1045.                     }
  1046.                 } else {
  1047.                     $fi = stat($cachename);
  1048.                     $cache_mtime = $fi[8];
  1049.                     //print cache_mtime, time()
  1050.                     if ($cache_mtime + $this->_cacheMaxAge < time()) {
  1051.                         // expired
  1052.                         $md5_wsdl = ''; // refetch
  1053.                     }
  1054.                 }
  1055.             }
  1056.         }
  1057.  
  1058.         if (!$md5_wsdl) {
  1059.             // Not cached or not using cache. Retrieve WSDL from URL
  1060.  
  1061.             // is it a local file?
  1062.             // this section should be replace by curl at some point
  1063.             if (!preg_match('/^(https?|file):\/\//', $wsdl_fname)) {
  1064.                 if (!file_exists($wsdl_fname)) {
  1065.                     return $this->_raiseSoapFault("Unable to read local WSDL $wsdl_fname", $wsdl_fname);
  1066.                 }
  1067.                 $file_data = file_get_contents($wsdl_fname);
  1068.             } else {
  1069.                 $uri = explode('?', $wsdl_fname);
  1070.                 $rq =& new HTTP_Request($uri[0], $proxy_params);
  1071.                 // the user agent HTTP_Request uses fouls things up
  1072.                 if (isset($uri[1])) {
  1073.                     $rq->addRawQueryString($uri[1]);
  1074.                 }
  1075.  
  1076.                 if (isset($proxy_params['proxy_host']) &&
  1077.                     isset($proxy_params['proxy_port']) &&
  1078.                     isset($proxy_params['proxy_user']) &&
  1079.                     isset($proxy_params['proxy_pass'])) {
  1080.                     $rq->setProxy($proxy_params['proxy_host'], $proxy_params['proxy_port'],
  1081.                                   $proxy_params['proxy_user'], $proxy_params['proxy_pass']);
  1082.                 } elseif (isset($proxy_params['proxy_host']) &&
  1083.                           isset($proxy_params['proxy_port'])) {
  1084.                     $rq->setProxy($proxy_params['proxy_host'], $proxy_params['proxy_port']);
  1085.                 }
  1086.  
  1087.                 $result = $rq->sendRequest();
  1088.                 if (PEAR::isError($result)) {
  1089.                     return $this->_raiseSoapFault("Unable to retrieve WSDL $wsdl_fname," . $rq->getResponseCode(), $wsdl_fname);
  1090.                 }
  1091.                 $file_data = $rq->getResponseBody();
  1092.                 if (!$file_data) {
  1093.                     return $this->_raiseSoapFault("Unable to retrieve WSDL $wsdl_fname, no http body", $wsdl_fname);
  1094.                 }
  1095.             }
  1096.  
  1097.             $md5_wsdl = md5($file_data);
  1098.  
  1099.             if ($this->_cacheUse) {
  1100.                 $fp = fopen($cachename, "wb");
  1101.                 fwrite($fp, $file_data);
  1102.                 fclose($fp);
  1103.             }
  1104.         }
  1105.         if ($this->_cacheUse && $cache && $cache != $md5_wsdl) {
  1106.             return $this->_raiseSoapFault("WSDL Checksum error!", $wsdl_fname);
  1107.         }
  1108.         return $file_data;
  1109.     }
  1110.  
  1111. }
  1112.  
  1113. class SOAP_WSDL_Parser extends SOAP_Base
  1114. {
  1115.  
  1116.     /**
  1117.      * Define internal arrays of bindings, ports, operations,
  1118.      * messages, etc.
  1119.      */
  1120.     var $currentMessage;
  1121.     var $currentOperation;
  1122.     var $currentPortType;
  1123.     var $currentBinding;
  1124.     var $currentPort;
  1125.  
  1126.     /**
  1127.      * Parser vars.
  1128.      */
  1129.     var $cache;
  1130.  
  1131.     var $tns = null;
  1132.     var $soapns = array('soap');
  1133.     var $uri = '';
  1134.     var $wsdl = null;
  1135.  
  1136.     var $status = '';
  1137.     var $element_stack = array();
  1138.     var $parentElement = '';
  1139.  
  1140.     var $schema = '';
  1141.     var $schemaStatus = '';
  1142.     var $schema_stack = array();
  1143.     var $currentComplexType;
  1144.     var $schema_element_stack = array();
  1145.     var $currentElement;
  1146.  
  1147.     /**
  1148.      * Constructor.
  1149.      */
  1150.     function SOAP_WSDL_Parser($uri, &$wsdl, $docs = false)
  1151.     {
  1152.         parent::SOAP_Base('WSDLPARSER');
  1153.         $this->cache =& new SOAP_WSDL_Cache($wsdl->cacheUse,
  1154.                                             $wsdl->cacheMaxAge,
  1155.                                             $wsdl->cacheDir);
  1156.         $this->uri = $uri;
  1157.         $this->wsdl = &$wsdl;
  1158.         $this->docs = $docs;
  1159.         $this->parse($uri);
  1160.     }
  1161.  
  1162.     function parse($uri)
  1163.     {
  1164.         // Check whether content has been read.
  1165.         $fd = $this->cache->get($uri, $this->wsdl->proxy);
  1166.         if (PEAR::isError($fd)) {
  1167.             return $this->_raiseSoapFault($fd);
  1168.         }
  1169.  
  1170.         // Create an XML parser.
  1171.         $parser = xml_parser_create();
  1172.         xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
  1173.         xml_set_object($parser, $this);
  1174.         xml_set_element_handler($parser, 'startElement', 'endElement');
  1175.         if ($this->docs) {
  1176.             xml_set_character_data_handler($parser, 'characterData');
  1177.         }
  1178.  
  1179.         if (!xml_parse($parser, $fd, true)) {
  1180.             $detail = sprintf('XML error on line %d: %s',
  1181.                               xml_get_current_line_number($parser),
  1182.                               xml_error_string(xml_get_error_code($parser)));
  1183.             return $this->_raiseSoapFault("Unable to parse WSDL file $uri\n$detail");
  1184.         }
  1185.         xml_parser_free($parser);
  1186.         return true;
  1187.     }
  1188.  
  1189.     /**
  1190.      * start-element handler
  1191.      */
  1192.     function startElement($parser, $name, $attrs)
  1193.     {
  1194.         // Get element prefix.
  1195.         $qname =& new QName($name);
  1196.         if ($qname->ns) {
  1197.             $ns = $qname->ns;
  1198.             if ($ns && ((!$this->tns && strcasecmp($qname->name, 'definitions') == 0) || $ns == $this->tns)) {
  1199.                 $name = $qname->name;
  1200.             }
  1201.         }
  1202.         $this->currentTag = $qname->name;
  1203.         $this->parentElement = '';
  1204.         $stack_size = count($this->element_stack);
  1205.         if ($stack_size) {
  1206.             $this->parentElement = $this->element_stack[$stack_size - 1];
  1207.         }
  1208.         $this->element_stack[] = $this->currentTag;
  1209.  
  1210.         // Find status, register data.
  1211.         switch ($this->status) {
  1212.         case 'types':
  1213.             // sect 2.2 wsdl:types
  1214.             // children: xsd:schema
  1215.             $parent_tag = '';
  1216.             $stack_size = count($this->schema_stack);
  1217.             if ($stack_size) {
  1218.                 $parent_tag = $this->schema_stack[$stack_size - 1];
  1219.             }
  1220.  
  1221.             switch ($qname->name) {
  1222.             case 'schema':
  1223.                 // No parent should be in the stack.
  1224.                 if (!$parent_tag || $parent_tag == 'types') {
  1225.                     if (array_key_exists('targetNamespace', $attrs)) {
  1226.                         $this->schema = $this->wsdl->getNamespaceAttributeName($attrs['targetNamespace']);
  1227.                     } else {
  1228.                         $this->schema = $this->wsdl->getNamespaceAttributeName($this->wsdl->tns);
  1229.                     }
  1230.                     $this->wsdl->complexTypes[$this->schema] = array();
  1231.                     $this->wsdl->elements[$this->schema] = array();
  1232.                 }
  1233.                 break;
  1234.  
  1235.             case 'complexType':
  1236.                 if ($parent_tag == 'schema') {
  1237.                     $this->currentComplexType = $attrs['name'];
  1238.                     if (!isset($attrs['namespace'])) {
  1239.                         $attrs['namespace'] = $this->schema;
  1240.                     }
  1241.                     $this->wsdl->complexTypes[$this->schema][$this->currentComplexType] = $attrs;
  1242.                     if (array_key_exists('base', $attrs)) {
  1243.                         $qn =& new QName($attrs['base']);
  1244.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = $qn->name;
  1245.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['namespace'] = $qn->ns;
  1246.                     } else {
  1247.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
  1248.                     }
  1249.                     $this->schemaStatus = 'complexType';
  1250.                 } else {
  1251.                     $this->wsdl->elements[$this->schema][$this->currentElement]['complex'] = true;
  1252.                 }
  1253.                 break;
  1254.  
  1255.             case 'element':
  1256.                 if (isset($attrs['type'])) {
  1257.                     $qn =& new QName($attrs['type']);
  1258.                     $attrs['type'] = $qn->name;
  1259.                     if ($qn->ns && array_key_exists($qn->ns, $this->wsdl->namespaces)) {
  1260.                         $attrs['namespace'] = $qn->ns;
  1261.                     }
  1262.                 }
  1263.  
  1264.                 $parentElement = '';
  1265.                 $stack_size = count($this->schema_element_stack);
  1266.                 if ($stack_size > 0) {
  1267.                     $parentElement = $this->schema_element_stack[$stack_size - 1];
  1268.                 }
  1269.  
  1270.                 if (isset($attrs['ref'])) {
  1271.                     $qn =& new QName($attrs['ref']);
  1272.                     $this->currentElement = $qn->name;
  1273.                 } else {
  1274.                     $this->currentElement = $attrs['name'];
  1275.                 }
  1276.                 $this->schema_element_stack[] = $this->currentElement;
  1277.                 if (!isset($attrs['namespace'])) {
  1278.                     $attrs['namespace'] = $this->schema;
  1279.                 }
  1280.  
  1281.                 if ($parent_tag == 'schema') {
  1282.                     $this->wsdl->elements[$this->schema][$this->currentElement] = $attrs;
  1283.                     $this->wsdl->elements[$this->schema][$this->currentElement]['complex'] = false;
  1284.                     $this->schemaStatus = 'element';
  1285.                 } elseif ($this->currentComplexType) {
  1286.                     // we're inside a complexType
  1287.                     if ((isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order']) &&
  1288.                          $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] == 'sequence')
  1289.                         && $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] == 'Array') {
  1290.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['arrayType'] = isset($attrs['type']) ? $attrs['type'] : null;
  1291.                     }
  1292.                     $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements'][$this->currentElement] = $attrs;
  1293.                 } else {
  1294.                     $this->wsdl->elements[$this->schema][$parentElement]['elements'][$this->currentElement] = $attrs;
  1295.                 }
  1296.                 break;
  1297.  
  1298.             case 'complexContent':
  1299.             case 'simpleContent':
  1300.                 break;
  1301.  
  1302.             case 'extension':
  1303.             case 'restriction':
  1304.                 if ($this->schemaStatus == 'complexType') {
  1305.                     if (!empty($attrs['base'])) {
  1306.                         $qn =& new QName($attrs['base']);
  1307.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = $qn->name;
  1308.  
  1309.                         // Types that extend from other types aren't
  1310.                         // *of* those types. Reflect this by denoting
  1311.                         // which type they extend. I'm leaving the
  1312.                         // 'type' setting here since I'm not sure what
  1313.                         // removing it might break at the moment.
  1314.                         if ($qname->name == 'extension') {
  1315.                             $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['extends'] = $qn->name;
  1316.                         }
  1317.                     } else {
  1318.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
  1319.                     }
  1320.                 }
  1321.                 break;
  1322.  
  1323.             case 'sequence':
  1324.                 if ($this->schemaStatus == 'complexType') {
  1325.                     $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
  1326.                     if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
  1327.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
  1328.                     }
  1329.                 }
  1330.                 break;
  1331.  
  1332.             case 'all':
  1333.                 $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
  1334.                 if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
  1335.                     $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
  1336.                 }
  1337.                 break;
  1338.  
  1339.             case 'choice':
  1340.                 $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['order'] = $qname->name;
  1341.                 if (!isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])) {
  1342.                     $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
  1343.                 }
  1344.  
  1345.             case 'attribute':
  1346.                 if ($this->schemaStatus == 'complexType') {
  1347.                     if (isset($attrs['name'])) {
  1348.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['attribute'][$attrs['name']] = $attrs;
  1349.                     } else {
  1350.                         if (isset($attrs['ref'])) {
  1351.                             $q =& new QName($attrs['ref']);
  1352.                             foreach ($attrs as $k => $v) {
  1353.                                 if ($k != 'ref' && strstr($k, $q->name)) {
  1354.                                     $vq =& new QName($v);
  1355.                                     if ($q->name == 'arrayType') {
  1356.                                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType][$q->name] = $vq->name. $vq->arrayInfo;
  1357.                                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Array';
  1358.                                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['namespace'] = $vq->ns;
  1359.                                     } else {
  1360.                                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType][$q->name] = $vq->name;
  1361.                                     }
  1362.                                 }
  1363.                             }
  1364.                         }
  1365.                     }
  1366.                 }
  1367.                 break;
  1368.             }
  1369.  
  1370.             $this->schema_stack[] = $qname->name;
  1371.             break;
  1372.  
  1373.         case 'message':
  1374.             // sect 2.3 wsdl:message child wsdl:part
  1375.             switch ($qname->name) {
  1376.             case 'part':
  1377.                 $qn = null;
  1378.                 if (isset($attrs['type'])) {
  1379.                     $qn =& new QName($attrs['type']);
  1380.                 } elseif (isset($attrs['element'])) {
  1381.                     $qn =& new QName($attrs['element']);
  1382.                 }
  1383.                 if ($qn) {
  1384.                     $attrs['type'] = $qn->name;
  1385.                     $attrs['namespace'] = $qn->ns;
  1386.                 }
  1387.                 $this->wsdl->messages[$this->currentMessage][$attrs['name']] = $attrs;
  1388.                 // error in wsdl
  1389.  
  1390.             case 'documentation':
  1391.                 break;
  1392.  
  1393.             default:
  1394.                 break;
  1395.             }
  1396.             break;
  1397.  
  1398.         case 'portType':
  1399.             // sect 2.4
  1400.             switch ($qname->name) {
  1401.             case 'operation':
  1402.                 // attributes: name
  1403.                 // children: wsdl:input wsdl:output wsdl:fault
  1404.                 $this->currentOperation = $attrs['name'];
  1405.                 $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation] = $attrs;
  1406.                 break;
  1407.  
  1408.             case 'input':
  1409.             case 'output':
  1410.             case 'fault':
  1411.                 // wsdl:input wsdl:output wsdl:fault
  1412.                 // attributes: name message parameterOrder(optional)
  1413.                 if ($this->currentOperation) {
  1414.                     if (isset($this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name])) {
  1415.                         $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name] = array_merge($this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name], $attrs);
  1416.                     } else {
  1417.                         $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name] = $attrs;
  1418.                     }
  1419.                     if (array_key_exists('message', $attrs)) {
  1420.                         $qn =& new QName($attrs['message']);
  1421.                         $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name]['message'] = $qn->name;
  1422.                         $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation][$name]['namespace'] = $qn->ns;
  1423.                     }
  1424.                 }
  1425.                 break;
  1426.  
  1427.             case 'documentation':
  1428.                 break;
  1429.  
  1430.             default:
  1431.                 break;
  1432.             }
  1433.             break;
  1434.  
  1435.         case 'binding':
  1436.             $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL;
  1437.             switch ($ns) {
  1438.             case SCHEMA_SOAP:
  1439.             case SCHEMA_SOAP12:
  1440.                 // this deals with wsdl section 3 soap binding
  1441.                 switch ($qname->name) {
  1442.                 case 'binding':
  1443.                     // sect 3.3
  1444.                     // soap:binding, attributes: transport(required), style(optional, default = document)
  1445.                     // if style is missing, it is assumed to be 'document'
  1446.                     if (!isset($attrs['style'])) {
  1447.                         $attrs['style'] = 'document';
  1448.                     }
  1449.                     $this->wsdl->bindings[$this->currentBinding] = array_merge($this->wsdl->bindings[$this->currentBinding], $attrs);
  1450.                     break;
  1451.  
  1452.                 case 'operation':
  1453.                     // sect 3.4
  1454.                     // soap:operation, attributes: soapAction(required), style(optional, default = soap:binding:style)
  1455.                     if (!isset($attrs['style'])) {
  1456.                         $attrs['style'] = $this->wsdl->bindings[$this->currentBinding]['style'];
  1457.                     }
  1458.                     if (isset($this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation])) {
  1459.                         $this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation] = array_merge($this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation], $attrs);
  1460.                     } else {
  1461.                         $this->wsdl->bindings[$this->currentBinding]['operations'][$this->currentOperation] = $attrs;
  1462.                     }
  1463.                     break;
  1464.  
  1465.                 case 'body':
  1466.                     // sect 3.5
  1467.                     // soap:body attributes:
  1468.                     // part - optional.  listed parts must appear in body, missing means all parts appear in body
  1469.                     // use - required. encoded|literal
  1470.                     // encodingStyle - optional.  space seperated list of encodings (uri's)
  1471.                     $this->wsdl->bindings[$this->currentBinding]
  1472.                                     ['operations'][$this->currentOperation][$this->opStatus] = $attrs;
  1473.                     break;
  1474.  
  1475.                 case 'fault':
  1476.                     // sect 3.6
  1477.                     // soap:fault attributes: name use  encodingStyle namespace
  1478.                     $this->wsdl->bindings[$this->currentBinding]
  1479.                                     ['operations'][$this->currentOperation][$this->opStatus] = $attrs;
  1480.                     break;
  1481.  
  1482.                 case 'header':
  1483.                     // sect 3.7
  1484.                     // soap:header attributes: message part use encodingStyle namespace
  1485.                     $this->wsdl->bindings[$this->currentBinding]
  1486.                                     ['operations'][$this->currentOperation][$this->opStatus]['headers'][] = $attrs;
  1487.                     break;
  1488.  
  1489.                 case 'headerfault':
  1490.                     // sect 3.7
  1491.                     // soap:header attributes: message part use encodingStyle namespace
  1492.                     $header = count($this->wsdl->bindings[$this->currentBinding]
  1493.                                     ['operations'][$this->currentOperation][$this->opStatus]['headers'])-1;
  1494.                     $this->wsdl->bindings[$this->currentBinding]
  1495.                                     ['operations'][$this->currentOperation][$this->opStatus]['headers'][$header]['fault'] = $attrs;
  1496.                     break;
  1497.  
  1498.                 case 'documentation':
  1499.                     break;
  1500.  
  1501.                 default:
  1502.                     // error!  not a valid element inside binding
  1503.                     break;
  1504.                 }
  1505.                 break;
  1506.  
  1507.             case SCHEMA_WSDL:
  1508.                 // XXX verify correct namespace
  1509.                 // for now, default is the 'wsdl' namespace
  1510.                 // other possible namespaces include smtp, http, etc. for alternate bindings
  1511.                 switch ($qname->name) {
  1512.                 case 'operation':
  1513.                     // sect 2.5
  1514.                     // wsdl:operation attributes: name
  1515.                     $this->currentOperation = $attrs['name'];
  1516.                     break;
  1517.  
  1518.                 case 'output':
  1519.                 case 'input':
  1520.                 case 'fault':
  1521.                     // sect 2.5
  1522.                     // wsdl:input attributes: name
  1523.                     $this->opStatus = $qname->name;
  1524.                     break;
  1525.  
  1526.                 case 'documentation':
  1527.                     break;
  1528.  
  1529.                 default:
  1530.                     break;
  1531.                 }
  1532.                 break;
  1533.  
  1534.             case SCHEMA_WSDL_HTTP:
  1535.                 switch ($qname->name) {
  1536.                 case 'binding':
  1537.                     // sect 4.4
  1538.                     // http:binding attributes: verb
  1539.                     // parent: wsdl:binding
  1540.                     $this->wsdl->bindings[$this->currentBinding] = array_merge($this->wsdl->bindings[$this->currentBinding], $attrs);
  1541.                     break;
  1542.  
  1543.                 case 'operation':
  1544.                     // sect 4.5
  1545.                     // http:operation attributes: location
  1546.                     // parent: wsdl:operation
  1547.                     $this->wsdl->bindings[$this->currentBinding]['operations']
  1548.                                                         [$this->currentOperation] = $attrs;
  1549.                     break;
  1550.  
  1551.                 case 'urlEncoded':
  1552.                     // sect 4.6
  1553.                     // http:urlEncoded attributes: location
  1554.                     // parent: wsdl:input wsdl:output etc.
  1555.                     $this->wsdl->bindings[$this->currentBinding]['operations'][$this->opStatus]
  1556.                                                         [$this->currentOperation]['uri'] = 'urlEncoded';
  1557.                     break;
  1558.  
  1559.                 case 'urlReplacement':
  1560.                     // sect 4.7
  1561.                     // http:urlReplacement attributes: location
  1562.                     // parent: wsdl:input wsdl:output etc.
  1563.                     $this->wsdl->bindings[$this->currentBinding]['operations'][$this->opStatus]
  1564.                                                         [$this->currentOperation]['uri'] = 'urlReplacement';
  1565.                     break;
  1566.  
  1567.                 case 'documentation':
  1568.                     break;
  1569.  
  1570.                 default:
  1571.                     // error
  1572.                     break;
  1573.                 }
  1574.  
  1575.             case SCHEMA_MIME:
  1576.                 // sect 5
  1577.                 // all mime parts are children of wsdl:input, wsdl:output, etc.
  1578.                 // unsuported as of yet
  1579.                 switch ($qname->name) {
  1580.                 case 'content':
  1581.                     // sect 5.3 mime:content
  1582.                     // <mime:content part="nmtoken"? type="string"?/>
  1583.                     // part attribute only required if content is child of multipart related,
  1584.                     //        it contains the name of the part
  1585.                     // type attribute contains the mime type
  1586.                 case 'multipartRelated':
  1587.                     // sect 5.4 mime:multipartRelated
  1588.                 case 'part':
  1589.                 case 'mimeXml':
  1590.                     // sect 5.6 mime:mimeXml
  1591.                     // <mime:mimeXml part="nmtoken"?/>
  1592.                     //
  1593.                 case 'documentation':
  1594.                     break;
  1595.  
  1596.                 default:
  1597.                     // error
  1598.                     break;
  1599.                 }
  1600.  
  1601.             case SCHEMA_DIME:
  1602.                 // DIME is defined in:
  1603.                 // http://gotdotnet.com/team/xml_wsspecs/dime/WSDL-Extension-for-DIME.htm
  1604.                 // all DIME parts are children of wsdl:input, wsdl:output, etc.
  1605.                 // unsuported as of yet
  1606.                 switch ($qname->name) {
  1607.                 case 'message':
  1608.                     // sect 4.1 dime:message
  1609.                     // appears in binding section
  1610.                     $this->wsdl->bindings[$this->currentBinding]['dime'] = $attrs;
  1611.                     break;
  1612.  
  1613.                 default:
  1614.                     break;
  1615.                 }
  1616.  
  1617.             default:
  1618.                 break;
  1619.             }
  1620.             break;
  1621.  
  1622.         case 'service':
  1623.             $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL;
  1624.  
  1625.             switch ($qname->name) {
  1626.             case 'port':
  1627.                 // sect 2.6 wsdl:port attributes: name binding
  1628.                 $this->currentPort = $attrs['name'];
  1629.                 $this->wsdl->services[$this->currentService]['ports'][$this->currentPort] = $attrs;
  1630.                 // XXX hack to deal with binding namespaces
  1631.                 $qn =& new QName($attrs['binding']);
  1632.                 $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['binding'] = $qn->name;
  1633.                 $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['namespace'] = $qn->ns;
  1634.                 break;
  1635.  
  1636.             case 'address':
  1637.                 $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['address'] = $attrs;
  1638.                 // what TYPE of port is it?  SOAP or HTTP?
  1639.                 $ns = $qname->ns ? $this->wsdl->namespaces[$qname->ns] : SCHEMA_WSDL;
  1640.                 switch ($ns) {
  1641.                 case SCHEMA_WSDL_HTTP:
  1642.                     $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='http';
  1643.                     break;
  1644.  
  1645.                 case SCHEMA_SOAP:
  1646.                     $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='soap';
  1647.                     break;
  1648.  
  1649.                 default:
  1650.                     // Shouldn't happen, we'll assume SOAP.
  1651.                     $this->wsdl->services[$this->currentService]['ports'][$this->currentPort]['type']='soap';
  1652.                 }
  1653.  
  1654.                 break;
  1655.  
  1656.             case 'documentation':
  1657.                 break;
  1658.  
  1659.             default:
  1660.                 break;
  1661.             }
  1662.         }
  1663.  
  1664.         // Top level elements found under wsdl:definitions.
  1665.         switch ($qname->name) {
  1666.         case 'import':
  1667.             // sect 2.1.1 wsdl:import attributes: namespace location
  1668.             if ((isset($attrs['location']) || isset($attrs['schemaLocation'])) &&
  1669.                 !isset($this->wsdl->imports[$attrs['namespace']])) {
  1670.                 $uri = isset($attrs['location']) ? $attrs['location'] : $attrs['schemaLocation'];
  1671.                 $location = @parse_url($uri);
  1672.                 if (!isset($location['scheme'])) {
  1673.                     $base = @parse_url($this->uri);
  1674.                     $uri = $this->mergeUrl($base, $uri);
  1675.                 }
  1676.  
  1677.                 $this->wsdl->imports[$attrs['namespace']] = $attrs;
  1678.                 $import_parser_class = get_class($this);
  1679.                 $import_parser =& new $import_parser_class($uri, $this->wsdl, $this->docs);
  1680.                 if ($import_parser->fault) {
  1681.                     unset($this->wsdl->imports[$attrs['namespace']]);
  1682.                     return false;
  1683.                 }
  1684.                 $this->currentImport = $attrs['namespace'];
  1685.             }
  1686.             // Continue on to the 'types' case - lack of break; is
  1687.             // intentional.
  1688.  
  1689.         case 'types':
  1690.             // sect 2.2 wsdl:types
  1691.             $this->status = 'types';
  1692.             break;
  1693.  
  1694.         case 'schema':
  1695.             // We can hit this at the top level if we've been asked to
  1696.             // import an XSD file.
  1697.             if (!empty($attrs['targetNamespace'])) {
  1698.                 $this->schema = $this->wsdl->getNamespaceAttributeName($attrs['targetNamespace']);
  1699.             } else {
  1700.                 $this->schema = $this->wsdl->getNamespaceAttributeName($this->wsdl->tns);
  1701.             }
  1702.             $this->wsdl->complexTypes[$this->schema] = array();
  1703.             $this->wsdl->elements[$this->schema] = array();
  1704.             $this->schema_stack[] = $qname->name;
  1705.             $this->status = 'types';
  1706.             break;
  1707.  
  1708.         case 'message':
  1709.             // sect 2.3 wsdl:message attributes: name children:wsdl:part
  1710.             $this->status = 'message';
  1711.             if (isset($attrs['name'])) {
  1712.                 $this->currentMessage = $attrs['name'];
  1713.                 $this->wsdl->messages[$this->currentMessage] = array();
  1714.             }
  1715.             break;
  1716.  
  1717.         case 'portType':
  1718.             // sect 2.4 wsdl:portType
  1719.             // attributes: name
  1720.             // children: wsdl:operation
  1721.             $this->status = 'portType';
  1722.             $this->currentPortType = $attrs['name'];
  1723.             $this->wsdl->portTypes[$this->currentPortType] = array();
  1724.             break;
  1725.  
  1726.         case 'binding':
  1727.             // sect 2.5 wsdl:binding attributes: name type
  1728.             // children: wsdl:operation soap:binding http:binding
  1729.             if ($qname->ns && $qname->ns != $this->tns) {
  1730.                 break;
  1731.             }
  1732.             $this->status = 'binding';
  1733.             $this->currentBinding = $attrs['name'];
  1734.             $qn =& new QName($attrs['type']);
  1735.             $this->wsdl->bindings[$this->currentBinding]['type'] = $qn->name;
  1736.             $this->wsdl->bindings[$this->currentBinding]['namespace'] = $qn->ns;
  1737.             break;
  1738.  
  1739.         case 'service':
  1740.             // sect 2.7 wsdl:service attributes: name children: ports
  1741.             $this->currentService = $attrs['name'];
  1742.             $this->wsdl->services[$this->currentService]['ports'] = array();
  1743.             $this->status = 'service';
  1744.             break;
  1745.  
  1746.         case 'definitions':
  1747.             // sec 2.1 wsdl:definitions
  1748.             // attributes: name targetNamespace xmlns:*
  1749.             // children: wsdl:import wsdl:types wsdl:message wsdl:portType wsdl:binding wsdl:service
  1750.             $this->wsdl->definition = $attrs;
  1751.             foreach ($attrs as $key => $value) {
  1752.                 if (strstr($key, 'xmlns:') !== false) {
  1753.                     $qn =& new QName($key);
  1754.                     // XXX need to refactor ns handling.
  1755.                     $this->wsdl->namespaces[$qn->name] = $value;
  1756.                     $this->wsdl->ns[$value] = $qn->name;
  1757.                     if ($key == 'targetNamespace' ||
  1758.                         strcasecmp($value,SOAP_SCHEMA) == 0) {
  1759.                         $this->soapns[] = $qn->name;
  1760.                     } else {
  1761.                         if (in_array($value, $this->_XMLSchema)) {
  1762.                             $this->wsdl->xsd = $value;
  1763.                         }
  1764.                     }
  1765.                 }
  1766.             }
  1767.             if (isset($ns) && $ns) {
  1768.                 $namespace = 'xmlns:' . $ns;
  1769.                 if (!$this->wsdl->definition[$namespace]) {
  1770.                     return $this->_raiseSoapFault("parse error, no namespace for $namespace", $this->uri);
  1771.                 }
  1772.                 $this->tns = $ns;
  1773.             }
  1774.             break;
  1775.         }
  1776.     }
  1777.  
  1778.     /**
  1779.      * end-element handler.
  1780.      */
  1781.     function endElement($parser, $name)
  1782.     {
  1783.         $stacksize = count($this->element_stack);
  1784.         if ($stacksize) {
  1785.             if ($this->element_stack[$stacksize - 1] == 'definitions') {
  1786.                 $this->status = '';
  1787.             }
  1788.             array_pop($this->element_stack);
  1789.         }
  1790.  
  1791.         if (stristr($name, 'schema')) {
  1792.             array_pop($this->schema_stack);
  1793.             $this->schema = '';
  1794.         }
  1795.  
  1796.         if ($this->schema) {
  1797.             array_pop($this->schema_stack);
  1798.             if (count($this->schema_stack) <= 1) {
  1799.                 /* Correct the type for sequences with multiple
  1800.                  * elements. */
  1801.                 if (isset($this->currentComplexType) && isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'])
  1802.                     && $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] == 'Array'
  1803.                     && array_key_exists('elements', $this->wsdl->complexTypes[$this->schema][$this->currentComplexType])
  1804.                     && count($this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements']) > 1) {
  1805.                         $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['type'] = 'Struct';
  1806.                 }
  1807.             }
  1808.             if (stristr($name, 'complexType')) {
  1809.                 $this->currentComplexType = '';
  1810.                 if (count($this->schema_element_stack)) {
  1811.                     $this->currentElement = array_pop($this->schema_element_stack);
  1812.                 } else {
  1813.                     $this->currentElement = '';
  1814.                 }
  1815.             } elseif (stristr($name, 'element')) {
  1816.                 if (count($this->schema_element_stack)) {
  1817.                     $this->currentElement = array_pop($this->schema_element_stack);
  1818.                 } else {
  1819.                     $this->currentElement = '';
  1820.                 }
  1821.             }
  1822.         }
  1823.     }
  1824.  
  1825.     /**
  1826.      * Element content handler.
  1827.      */
  1828.     function characterData($parser, $data)
  1829.     {
  1830.         // Store the documentation in the WSDL file.
  1831.         if ($this->currentTag == 'documentation') {
  1832.             $data = trim(preg_replace('/\s+/', ' ', $data));
  1833.             if (!strlen($data)) {
  1834.                 return;
  1835.             }
  1836.  
  1837.             switch ($this->status) {
  1838.             case 'service':
  1839.                 $ptr =& $this->wsdl->services[$this->currentService];
  1840.                 break;
  1841.  
  1842.             case 'portType':
  1843.                 $ptr =& $this->wsdl->portTypes[$this->currentPortType][$this->currentOperation];
  1844.                 break;
  1845.  
  1846.             case 'binding':
  1847.                 $ptr =& $this->wsdl->bindings[$this->currentBinding];
  1848.                 break;
  1849.  
  1850.             case 'message':
  1851.                 $ptr =& $this->wsdl->messages[$this->currentMessage];
  1852.                 break;
  1853.  
  1854.             case 'operation':
  1855.                 break;
  1856.  
  1857.             case 'types':
  1858.                 if (isset($this->currentComplexType) &&
  1859.                     isset($this->wsdl->complexTypes[$this->schema][$this->currentComplexType])) {
  1860.                     if ($this->currentElement) {
  1861.                         $ptr =& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType]['elements'][$this->currentElement];
  1862.                     } else {
  1863.                         $ptr =& $this->wsdl->complexTypes[$this->schema][$this->currentComplexType];
  1864.                     }
  1865.                 }
  1866.                 break;
  1867.             }
  1868.  
  1869.             if (isset($ptr)) {
  1870.                 if (!isset($ptr['documentation'])) {
  1871.                     $ptr['documentation'] = '';
  1872.                 } else {
  1873.                     $ptr['documentation'] .= ' ';
  1874.                 }
  1875.                 $ptr['documentation'] .= $data;
  1876.             }
  1877.         }
  1878.     }
  1879.  
  1880.     /**
  1881.      * $parsed is an array returned by parse_url().
  1882.      *
  1883.      * @access private
  1884.      */
  1885.     function mergeUrl($parsed, $path)
  1886.     {
  1887.         if (!is_array($parsed)) {
  1888.             return false;
  1889.         }
  1890.  
  1891.         $uri = '';
  1892.         if (!empty($parsed['scheme'])) {
  1893.             $sep = (strtolower($parsed['scheme']) == 'mailto' ? ':' : '://');
  1894.             $uri = $parsed['scheme'] . $sep;
  1895.         }
  1896.  
  1897.         if (isset($parsed['pass'])) {
  1898.             $uri .= "$parsed[user]:$parsed[pass]@";
  1899.         } elseif (isset($parsed['user'])) {
  1900.             $uri .= "$parsed[user]@";
  1901.         }
  1902.  
  1903.         if (isset($parsed['host'])) {
  1904.             $uri .= $parsed['host'];
  1905.         }
  1906.         if (isset($parsed['port'])) {
  1907.             $uri .= ":$parsed[port]";
  1908.         }
  1909.         if ($path[0] != '/' && isset($parsed['path'])) {
  1910.             if ($parsed['path'][strlen($parsed['path']) - 1] != '/') {
  1911.                 $path = dirname($parsed['path']) . '/' . $path;
  1912.             } else {
  1913.                 $path = $parsed['path'] . $path;
  1914.             }
  1915.             $path = $this->_normalize($path);
  1916.         }
  1917.         $sep = $path[0] == '/' ? '' : '/';
  1918.         $uri .= $sep . $path;
  1919.  
  1920.         return $uri;
  1921.     }
  1922.  
  1923.     function _normalize($path_str)
  1924.     {
  1925.         $pwd = '';
  1926.         $strArr = preg_split('/(\/)/', $path_str, -1, PREG_SPLIT_NO_EMPTY);
  1927.         $pwdArr = '';
  1928.         $j = 0;
  1929.         for ($i = 0; $i < count($strArr); $i++) {
  1930.             if ($strArr[$i] != ' ..') {
  1931.                 if ($strArr[$i] != ' .') {
  1932.                     $pwdArr[$j] = $strArr[$i];
  1933.                     $j++;
  1934.                 }
  1935.             } else {
  1936.                 array_pop($pwdArr);
  1937.                 $j--;
  1938.             }
  1939.         }
  1940.         $pStr = implode('/', $pwdArr);
  1941.         $pwd = (strlen($pStr) > 0) ? ('/' . $pStr) : '/';
  1942.         return $pwd;
  1943.     }
  1944.  
  1945. }
  1946.  
  1947. /**
  1948.  * Parses the types and methods used in web service objects into the internal
  1949.  * data structures used by SOAP_WSDL.
  1950.  *
  1951.  * Assumes the SOAP_WSDL class is unpopulated to start with.
  1952.  *
  1953.  * @author Chris Coe <info@intelligentstreaming.com>
  1954.  */
  1955. class SOAP_WSDL_ObjectParser extends SOAP_Base
  1956. {
  1957.     /**
  1958.      * Target namespace for the WSDL document will have the following
  1959.      * prefix.
  1960.      */
  1961.     var $tnsPrefix = 'tns';
  1962.  
  1963.     /**
  1964.      * Reference to the SOAP_WSDL object to populate.
  1965.      */
  1966.     var $wsdl = null;
  1967.  
  1968.     /** Constructor
  1969.      *
  1970.      * @param  $objects Reference to the object or array of objects to parse
  1971.      * @param  $wsdl Reference to the SOAP_WSDL object to populate
  1972.      * @param  $targetNamespace The target namespace of schema types etc.
  1973.      * @param  $service_name Name of the WSDL <service>
  1974.      * @param  $service_desc Optional description of the WSDL <service>
  1975.      */
  1976.     function SOAP_WSDL_ObjectParser(&$objects, &$wsdl, $targetNamespace, $service_name, $service_desc = '')
  1977.     {
  1978.         parent::SOAP_Base('WSDLOBJECTPARSER');
  1979.  
  1980.         $this->wsdl = &$wsdl;
  1981.  
  1982.         // Set up the SOAP_WSDL object
  1983.         $this->_initialise($service_name);
  1984.  
  1985.         // Parse each web service object
  1986.         $wsdl_ref = (is_array($objects)? $objects : array(&$objects));
  1987.  
  1988.         foreach ($wsdl_ref as $ref_item) {
  1989.             if (!is_object($ref_item))
  1990.                 return $this->_raiseSoapFault('Invalid web service object passed to object parser', 'urn:' . get_class($object));
  1991.  
  1992.             if ($this->_parse($ref_item, $targetNamespace, $service_name) != true)
  1993.                 break;
  1994.         }
  1995.  
  1996.         // Build bindings from abstract data.
  1997.         if ($this->fault == null) {
  1998.             $this->_generateBindingsAndServices($targetNamespace, $service_name, $service_desc);
  1999.         }
  2000.     }
  2001.  
  2002.     /**
  2003.      * Initialise the SOAP_WSDL tree (destructive).
  2004.      *
  2005.      * If the object has already been initialised, the only effect
  2006.      * will be to change the tns namespace to the new service name.
  2007.      *
  2008.      * @param  $service_name Name of the WSDL <service>
  2009.      * @access private
  2010.      */
  2011.     function _initialise($service_name)
  2012.     {
  2013.         // Set up the basic namespaces that all WSDL definitions use.
  2014.         $this->wsdl->namespaces['wsdl'] = SCHEMA_WSDL;                                      // WSDL language
  2015.         $this->wsdl->namespaces['soap'] = SCHEMA_SOAP;                                      // WSDL SOAP bindings
  2016.         $this->wsdl->namespaces[$this->tnsPrefix] = 'urn:' . $service_name;                 // Target namespace
  2017.         $this->wsdl->namespaces['xsd'] = array_search('xsd', $this->_namespaces);           // XML Schema
  2018.         $this->wsdl->namespaces['SOAP-ENC'] = array_search('SOAP-ENC', $this->_namespaces); // SOAP types
  2019.  
  2020.         // XXX Refactor $namespace/$ns for Shane :-)
  2021.         unset($this->wsdl->ns['urn:' . $service_name]);
  2022.         $this->wsdl->ns += array_flip($this->wsdl->namespaces);
  2023.  
  2024.         // Imports are not implemented in WSDL generation from classes.
  2025.         // *** <wsdl:import> ***
  2026.     }
  2027.  
  2028.     /**
  2029.      * Parser - takes a single object to add to tree (non-destructive).
  2030.      *
  2031.      * @access private
  2032.      *
  2033.      * @param object $object           Reference to the object to parse.
  2034.      * @param string $schemaNamespace
  2035.      * @param string $service_name     Name of the WSDL <service>.
  2036.      */
  2037.     function _parse(&$object, $schemaNamespace, $service_name)
  2038.     {
  2039.         // Create namespace prefix for the schema
  2040.         list($schPrefix,) = $this->_getTypeNs('{' . $schemaNamespace . '}');
  2041.  
  2042.         // Parse all the types defined by the object in whatever
  2043.         // schema language we are using (currently __typedef arrays)
  2044.         // *** <wsdl:types> ***
  2045.         foreach ($object->__typedef as $typeName => $typeValue) {
  2046.             // Get/create namespace definition
  2047.             list($nsPrefix, $typeName) = $this->_getTypeNs($typeName);
  2048.  
  2049.             // Create type definition
  2050.             $this->wsdl->complexTypes[$schPrefix][$typeName] = array('name' => $typeName);
  2051.             $thisType =& $this->wsdl->complexTypes[$schPrefix][$typeName];
  2052.  
  2053.             // According to Dmitri's documentation, __typedef comes in two
  2054.             // flavors:
  2055.             // Array = array(array("item" => "value"))
  2056.             // Struct = array("item1" => "value1", "item2" => "value2", ...)
  2057.             if (is_array($typeValue)) {
  2058.                 if (is_array(current($typeValue)) && count($typeValue) == 1
  2059.                     && count(current($typeValue)) == 1) {
  2060.                     // It's an array
  2061.                     $thisType['type'] = 'Array';
  2062.                     $nsType = current(current($typeValue));
  2063.                     list($nsPrefix, $typeName) = $this->_getTypeNs($nsType);
  2064.                     $thisType['namespace'] = $nsPrefix;
  2065.                     $thisType['arrayType'] = $typeName . '[]';
  2066.                 } elseif (!is_array(current($typeValue))) {
  2067.                     // It's a struct
  2068.                     $thisType['type'] = 'Struct';
  2069.                     $thisType['order'] = 'all';
  2070.                     $thisType['namespace'] = $nsPrefix;
  2071.                     $thisType['elements'] = array();
  2072.  
  2073.                     foreach ($typeValue as $elementName => $elementType) {
  2074.                         list($nsPrefix, $typeName) = $this->_getTypeNs($elementType);
  2075.                         $thisType['elements'][$elementName]['name'] = $elementName;
  2076.                         $thisType['elements'][$elementName]['type'] = $typeName;
  2077.                         $thisType['elements'][$elementName]['namespace'] = $nsPrefix;
  2078.                     }
  2079.                 } else {
  2080.                     // It's erroneous
  2081.                     return $this->_raiseSoapFault("The type definition for $nsPrefix:$typeName is invalid.", 'urn:' . get_class($object));
  2082.                 }
  2083.             } else {
  2084.                 // It's erroneous
  2085.                 return $this->_raiseSoapFault("The type definition for $nsPrefix:$typeName is invalid.", 'urn:' . get_class($object));
  2086.             }
  2087.         }
  2088.  
  2089.         // Create an empty element array with the target namespace
  2090.         // prefix, to match the results of WSDL parsing.
  2091.         $this->wsdl->elements[$schPrefix] = array();
  2092.  
  2093.         // Populate tree with message information
  2094.         // *** <wsdl:message> ***
  2095.         foreach ($object->__dispatch_map as $operationName => $messages) {
  2096.             foreach ($messages as $messageType => $messageParts) {
  2097.                 unset($thisMessage);
  2098.  
  2099.                 switch ($messageType) {
  2100.                 case 'in':
  2101.                     $this->wsdl->messages[$operationName . 'Request'] = array();
  2102.                     $thisMessage =& $this->wsdl->messages[$operationName . 'Request'];
  2103.                     break;
  2104.  
  2105.                 case 'out':
  2106.                     $this->wsdl->messages[$operationName . 'Response'] = array();
  2107.                     $thisMessage =& $this->wsdl->messages[$operationName . 'Response'];
  2108.                     break;
  2109.  
  2110.                 case 'alias':
  2111.                     // Do nothing
  2112.                     break;
  2113.  
  2114.                 default:
  2115.                     // Error condition
  2116.                     break;
  2117.                 }
  2118.  
  2119.                 if (isset($thisMessage)) {
  2120.                     foreach ($messageParts as $partName => $partType) {
  2121.                         list ($nsPrefix, $typeName) = $this->_getTypeNs($partType);
  2122.  
  2123.                         $thisMessage[$partName] = array(
  2124.                             'name' => $partName,
  2125.                             'type' => $typeName,
  2126.                             'namespace' => $nsPrefix
  2127.                             );
  2128.                     }
  2129.                 }
  2130.             }
  2131.         }
  2132.  
  2133.         // Populate tree with portType information
  2134.         // XXX Current implementation only supports one portType that
  2135.         // encompasses all of the operations available.
  2136.         // *** <wsdl:portType> ***
  2137.         if (!isset($this->wsdl->portTypes[$service_name . 'Port'])) {
  2138.             $this->wsdl->portTypes[$service_name . 'Port'] = array();
  2139.         }
  2140.         $thisPortType =& $this->wsdl->portTypes[$service_name . 'Port'];
  2141.  
  2142.         foreach ($object->__dispatch_map as $operationName => $messages) {
  2143.             $thisPortType[$operationName] = array('name' => $operationName);
  2144.  
  2145.             foreach ($messages as $messageType => $messageParts) {
  2146.                 switch ($messageType) {
  2147.                 case 'in':
  2148.                     $thisPortType[$operationName]['input'] = array(
  2149.                         'message' => $operationName . 'Request',
  2150.                         'namespace' => $this->tnsPrefix);
  2151.                     break;
  2152.  
  2153.                 case 'out':
  2154.                     $thisPortType[$operationName]['output'] = array(
  2155.                         'message' => $operationName . 'Response',
  2156.                         'namespace' => $this->tnsPrefix);
  2157.                     break;
  2158.                 }
  2159.             }
  2160.         }
  2161.  
  2162.         return true;
  2163.     }
  2164.  
  2165.     /**
  2166.      * Take all the abstract WSDL data and build concrete bindings and
  2167.      * services (destructive).
  2168.      *
  2169.      * XXX Current implementation discards $service_desc.
  2170.      *
  2171.      * @param  $schemaNamespace Namespace for types etc.
  2172.      * @param  $service_name Name of the WSDL <service>
  2173.      * @param  $service_desc Optional description of the WSDL <service>
  2174.      * @access private
  2175.      */
  2176.     function _generateBindingsAndServices($schemaNamespace, $service_name, $service_desc = '')
  2177.     {
  2178.         // Populate tree with bindings information
  2179.         // XXX Current implementation only supports one binding that
  2180.         // matches the single portType and all of its operations.
  2181.         // XXX Is this the correct use of $schemaNamespace here?
  2182.         // *** <wsdl:binding> ***
  2183.  
  2184.         $this->wsdl->bindings[$service_name . 'Binding'] = array(
  2185.                 'type' => $service_name . 'Port',
  2186.                 'namespace' => $this->tnsPrefix,
  2187.                 'style' => 'rpc',
  2188.                 'transport' => SCHEMA_SOAP_HTTP,
  2189.                 'operations' => array());
  2190.         $thisBinding =& $this->wsdl->bindings[$service_name . 'Binding'];
  2191.  
  2192.         foreach ($this->wsdl->portTypes[$service_name . 'Port'] as $operationName => $operationData) {
  2193.             $thisBinding['operations'][$operationName] = array(
  2194.                 'soapAction' => $schemaNamespace . '#' . $operationName,
  2195.                 'style' => $thisBinding['style']);
  2196.  
  2197.             foreach (array('input', 'output') as $messageType)
  2198.                 if (isset($operationData[$messageType])) {
  2199.                     $thisBinding['operations'][$operationName][$messageType] = array(
  2200.                             'use' => 'encoded',
  2201.                             'namespace' => $schemaNamespace,
  2202.                             'encodingStyle' => SOAP_SCHEMA_ENCODING);
  2203.                 }
  2204.         }
  2205.  
  2206.         // Populate tree with service information
  2207.         // XXX Current implementation supports one service which groups
  2208.         // all of the ports together, one port per binding
  2209.         // *** <wsdl:service> ***
  2210.  
  2211.         $this->wsdl->services[$service_name . 'Service'] = array('ports' => array());
  2212.         $thisService =& $this->wsdl->services[$service_name . 'Service']['ports'];
  2213.         $https = (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on')) ||
  2214.             getenv('SSL_PROTOCOL_VERSION');
  2215.  
  2216.         foreach ($this->wsdl->bindings as $bindingName => $bindingData) {
  2217.             $thisService[$bindingData['type']] = array(
  2218.                     'name' => $bindingData['type'],
  2219.                     'binding' => $bindingName,
  2220.                     'namespace' => $this->tnsPrefix,
  2221.                     'address' => array('location' =>
  2222.                         ($https ? 'https://' : 'http://') .
  2223.                         $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] .
  2224.                         (isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '')),
  2225.                     'type' => 'soap');
  2226.         }
  2227.  
  2228.         // Set service
  2229.         $this->wsdl->set_service($service_name . 'Service');
  2230.         $this->wsdl->uri = $this->wsdl->namespaces[$this->tnsPrefix];
  2231.  
  2232.         // Create WSDL definition
  2233.         // *** <wsdl:definitions> ***
  2234.  
  2235.         $this->wsdl->definition = array(
  2236.                 'name' => $service_name,
  2237.                 'targetNamespace' => $this->wsdl->namespaces[$this->tnsPrefix],
  2238.                 'xmlns' => SCHEMA_WSDL);
  2239.  
  2240.         foreach ($this->wsdl->namespaces as $nsPrefix => $namespace) {
  2241.             $this->wsdl->definition['xmlns:' . $nsPrefix] = $namespace;
  2242.         }
  2243.     }
  2244.  
  2245.     /**
  2246.      * This function is adapted from Dmitri V's implementation of
  2247.      * DISCO/WSDL generation. It separates namespace from type name in
  2248.      * a __typedef key and creates a new namespace entry in the WSDL
  2249.      * structure if the namespace has not been used before. The
  2250.      * namespace prefix and type name are returned. If no namespace is
  2251.      * specified, xsd is assumed.
  2252.      *
  2253.      * We will not need this function anymore once __typedef is
  2254.      * eliminated.
  2255.      */
  2256.     function _getTypeNs($type)
  2257.     {
  2258.         preg_match_all('/\{(.*)\}/sm', $type, $m);
  2259.         if (!empty($m[1][0])) {
  2260.             if (!isset($this->wsdl->ns[$m[1][0]])) {
  2261.                 $ns_pref = 'ns' . count($this->wsdl->namespaces);
  2262.                 $this->wsdl->ns[$m[1][0]] = $ns_pref;
  2263.                 $this->wsdl->namespaces[$ns_pref] = $m[1][0];
  2264.             }
  2265.             $typens = $this->wsdl->ns[$m[1][0]];
  2266.             $type = str_replace($m[0][0], '', $type);
  2267.         } else {
  2268.             $typens = 'xsd';
  2269.         }
  2270.  
  2271.         return array($typens, $type);
  2272.     }
  2273.  
  2274. }
  2275.